<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* {
  box-sizing: border-box;
}
body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 16px;
  margin: 2rem 1rem;
  max-width: 800px;
  margin: 0 auto;
  line-height: 1.2;
}
#autocomplete-container {
  position: relative;
  width: 100%;
}
#search-input {
  width: 100%;
  padding: 10px;
  font-size: 16px;
}
#suggestions {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  max-height: 300px;
  overflow-y: auto;
  border: 1px solid #ddd;
  background: white;
  z-index: 10;
  margin: 0 15px;
}
.suggestion-item {
  padding: 10px;
  cursor: pointer;
}
.suggestion-item:hover, .suggestion-item.selected {
  background-color: #f0f0f0;
}
#timeline, .timeline {
  position: relative;
  border-left: 2px solid #ccc;
  margin-left: 20px;
  padding-left: 20px;
  margin-top: 2em;
}
.event {
  margin-bottom: 20px;
  position: relative;
}
.event::before {
  content: '';
  position: absolute;
  left: -26px;
  top: 5px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #3b82f6;
}
.event-date {
  font-size: 0.875rem;
  color: #666;
}
.event-browser {
  font-weight: 600;
  color: #1f2937;
}
.event-version {
  color: #059669;
  margin-left: 4px;
}
.error {
  color: #dc2626;
  margin-top: 8px;
  display: none;
  padding: 8px;
  background: #fef2f2;
  border-radius: 4px;
}
.not-supported {
  margin-top: 20px;
  padding: 12px;
  background: #f3f4f6;
  border-radius: 4px;
}
.api-section {
  margin-top: 2rem;
  padding: 1rem;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
}
.api-section h2 {
  margin-top: 0;
  color: #1f2937;
  font-size: 1.5rem;
  word-break: break-word;
  line-height: 1.4;
}
.api-info {
  margin: 1rem 0;
}
.api-info-item {
  margin: 0.5rem 0;
}
.api-info-label {
  font-weight: 600;
  color: #4b5563;
}
.status-indicator {
  display: inline-block;
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
  font-size: 0.875rem;
  margin-right: 0.5rem;
}
.status-experimental {
  background-color: #fef3c7;
  color: #92400e;
}
.status-deprecated {
  background-color: #fee2e2;
  color: #991b1b;
}
.status-standard {
  background-color: #d1fae5;
  color: #065f46;
}
@media (max-width: 600px) {
  body {
    margin: 1rem;
    font-size: 14px;
  }
  #timeline {
    margin-left: 10px;
    padding-left: 10px;
  }
  .event::before {
    left: -16px;
  }
}
</style>
</head>
<body>
<h1>MDN Browser Support Timelines</h1>
<p><a href="https://simonwillison.net/2024/Nov/11/mdn-browser-support-timelines/">Background on this project</a></p>
<div id="autocomplete-container">
  <input type="text" id="search-input" placeholder="Search files...">
  <div id="suggestions"></div>
</div>

<div id="timeline-section">
  <div id="timeline"></div>
</div>

<div id="api-details"></div>

<script>
let allFiles = [];
let selectedFiles = [];

async function fetchAllFiles() {
  try {
    // First API call to get repository contents
    const contentsResponse = await fetch('https://api.github.com/repos/mdn/browser-compat-data/contents');
    
    if (!contentsResponse.ok) {
      throw new Error(`HTTP error! status: ${contentsResponse.status}`);
    }
    
    const contentsData = await contentsResponse.json();
    
    // Find the object with name: "api"
    const apiObject = contentsData.find(item => item.name === "api");
    
    if (!apiObject) {
      throw new Error('Could not find object with name "api"');
    }
    
    // Extract the git URL from _links
    const gitUrl = apiObject._links?.git;
    
    if (!gitUrl) {
      throw new Error('Git URL not found in _links');
    }
    
    // Second API call to fetch the git data
    const gitResponse = await fetch(gitUrl);
    
    if (!gitResponse.ok) {
      throw new Error(`HTTP error! status: ${gitResponse.status}`);
    }
    
    const gitData = await gitResponse.json();
    
    // Return the tree array from the git data
    if (!gitData.tree) {
      throw new Error('Tree data not found in git response');
    }
    
    return gitData.tree;
    
  } catch (error) {
    console.error('Error fetching GitHub data:', error);
    throw error;
  }
}

function updateUrlHash(filePath) {
  if (filePath) {
    history.pushState({ filePath }, '', `#${filePath}`);
  } else {
    history.pushState({ filePath: null }, '', window.location.pathname);
  }
}

function clearDisplay() {
  document.getElementById('search-input').value = '';
  document.getElementById('timeline').innerHTML = '';
  document.getElementById('api-details').innerHTML = '';
  document.getElementById('suggestions').innerHTML = '';
}

function getHashPath() {
  const hash = window.location.hash.slice(1);
  return hash ? decodeURIComponent(hash) : null;
}

function setupAutocomplete() {
  const searchInput = document.getElementById('search-input');
  const suggestionsContainer = document.getElementById('suggestions');

  searchInput.addEventListener('input', () => {
    const searchTerm = searchInput.value.toLowerCase();
    const regexPattern = searchTerm
      .split(' ')
      .filter(term => term.length > 0)  // Remove empty strings from multiple spaces
      .map(term => term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))  // Escape regex special chars
      .join('.*');
    const searchRegex = new RegExp(regexPattern);

    const filteredFiles = allFiles.filter(file => 
      searchRegex.test(file.toLowerCase())
      ).slice(0, 20);

    suggestionsContainer.innerHTML = '';

    filteredFiles.forEach((file, index) => {
      const suggestionItem = document.createElement('div');
      suggestionItem.textContent = file;
      suggestionItem.className = 'suggestion-item';
      suggestionItem.setAttribute('data-index', index);

      suggestionItem.addEventListener('click', () => {
        searchInput.value = file;
        suggestionsContainer.innerHTML = '';
        fetchBrowserCompatData(file);
      });

      suggestionsContainer.appendChild(suggestionItem);
    });
  });

  searchInput.addEventListener('keydown', handleKeyboardNavigation);
}

function handleKeyboardNavigation(e) {
  const suggestionsContainer = document.getElementById('suggestions');
  const suggestions = suggestionsContainer.children;
  
  if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
    e.preventDefault();
    if (suggestions.length > 0) {
      const currentSelected = suggestionsContainer.querySelector('.selected');
      const nextIndex = currentSelected 
        ? Math.min(parseInt(currentSelected.getAttribute('data-index')) + (e.key === 'ArrowDown' ? 1 : -1), suggestions.length - 1)
        : 0;
      
      if (currentSelected) currentSelected.classList.remove('selected');
      suggestions[nextIndex].classList.add('selected');
    }
  } else if (e.key === 'Enter') {
    const selectedItem = suggestionsContainer.querySelector('.selected') || 
                         suggestionsContainer.children[0];
    
    if (selectedItem) {
      e.preventDefault();
      const searchInput = document.getElementById('search-input');
      searchInput.value = selectedItem.textContent;
      suggestionsContainer.innerHTML = '';
      fetchBrowserCompatData(selectedItem.textContent);
    }
  }
}

function renderApiSection(data) {
  const apiDetails = document.getElementById('api-details');
  apiDetails.innerHTML = '';

  // Render main API section
  const mainSection = createApiSection(data.data.__compat, data.query);
  apiDetails.appendChild(mainSection);

  // Render sub-features
  Object.entries(data.data).forEach(([key, value]) => {
    if (key !== '__compat' && value.__compat) {
      const subSection = createApiSection(value.__compat, `${data.query}.${key}`);
      apiDetails.appendChild(subSection);
    }
  });
}

function createApiSection(compat, title) {
  const section = document.createElement('section');
  section.className = 'api-section';

  const heading = document.createElement('h2');
  heading.textContent = title;
  section.appendChild(heading);

  const info = document.createElement('div');
  info.className = 'api-info';

  // Add MDN link if available
  if (compat.mdn_url) {
    const mdnLink = document.createElement('div');
    mdnLink.className = 'api-info-item';
    const mdnUrl = compat.mdn_url.replace('/en-US/docs/Web/API/', 'https://developer.mozilla.org/en-US/docs/Web/API/');
    mdnLink.innerHTML = `<span class="api-info-label">MDN Documentation:</span> <a href="${mdnUrl}" target="_blank">${mdnUrl}</a>`;
    info.appendChild(mdnLink);
  }

  // Add specification link if available
  if (compat.spec_url) {
    const specLink = document.createElement('div');
    specLink.className = 'api-info-item';
    specLink.innerHTML = `<span class="api-info-label">Specification:</span> <a href="${compat.spec_url}" target="_blank">${compat.spec_url}</a>`;
    info.appendChild(specLink);
  }

  // Add status indicators
  if (compat.status) {
    const statusDiv = document.createElement('div');
    statusDiv.className = 'api-info-item';
    
    Object.entries(compat.status).forEach(([key, value]) => {
      if (value) {
        const status = document.createElement('span');
        status.className = `status-indicator status-${key}`;
        status.textContent = key.charAt(0).toUpperCase() + key.slice(1);
        statusDiv.appendChild(status);
      }
    });
    
    info.appendChild(statusDiv);
  }

  section.appendChild(info);

  // Add timeline for this section
  const timelineDiv = document.createElement('div');
  timelineDiv.className = 'timeline';
  const supportData = extractSupportData({ 
    browsers: compat.support
  });
  renderTimeline(supportData, timelineDiv);
  section.appendChild(timelineDiv);

  return section;
}

async function fetchBrowserCompatData(filePath) {
  try {
    updateUrlHash(filePath);
    const url = `https://bcd.developer.mozilla.org/bcd/api/v0/current/api.${filePath}.json`;
    const response = await fetch(url);
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const data = await response.json();
    const timelineData = extractSupportData(data);
    renderTimeline(timelineData);
    renderApiSection(data);
  } catch (error) {
    console.error('Error fetching browser compat data:', error);
    renderErrorMessage(error.message);
  }
}

function extractSupportData(data) {
  const browsers = data.browsers;
        
  const supportData = [];
  const notSupported = [];
        
  for (const [browserName, supportInfo] of Object.entries(browsers)) {
    if (!supportInfo[0]) continue;
        
    if (supportInfo[0].version_added === false) {
      notSupported.push(browsers[browserName]?.name || browserName);
      continue;
    }
        
    if (!supportInfo[0].version_added || !supportInfo[0].release_date) continue;
        
    supportData.push({
      browser: browsers[browserName]?.name || browserName,
      version: supportInfo[0].version_added,
      date: supportInfo[0].release_date
    });
  }
        
  return {
    supported: supportData.sort((a, b) => new Date(a.date) - new Date(b.date)),
    notSupported
  };
}

function formatDate(dateStr) {
  return new Date(dateStr).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  });
}

function renderTimeline(data, container = document.getElementById('timeline')) {
  container.innerHTML = '';
        
  data.supported.forEach(item => {
    const event = document.createElement('div');
    event.className = 'event';
    event.innerHTML = `
      <div class="event-date">${formatDate(item.date)}</div>
      <div>
        <span class="event-browser">${item.browser}</span>
        <span class="event-version">v${item.version}</span>
      </div>
    `;
    container.appendChild(event);
  });
   
  if (data.notSupported.length > 0) {
    const notSupportedDiv = document.createElement('div');
    notSupportedDiv.className = 'not-supported';
    notSupportedDiv.innerHTML = `
      <strong>Not Supported:</strong> ${data.notSupported.join(', ')}
    `;
    container.appendChild(notSupportedDiv);
  }
}

function renderErrorMessage(message) {
  const timeline = document.getElementById('timeline');
  timeline.innerHTML = `
    <div class="error" style="display: block;">
      Error: ${message}
    </div>
  `;
}

// Initialize the app
async function init() {
  allFiles = (await fetchAllFiles('mdn/browser-compat-data', 'api')).map(file => file.path.split(".json")[0]);
  setupAutocomplete();

  // Check for hash in URL and load that file if present
  const hashPath = getHashPath();
  if (hashPath) {
    document.getElementById('search-input').value = hashPath;
    fetchBrowserCompatData(hashPath);
  }
}

// Handle back/forward navigation
window.addEventListener('popstate', (event) => {
  const hashPath = getHashPath();
  if (hashPath) {
    document.getElementById('search-input').value = hashPath;
    fetchBrowserCompatData(hashPath);
  } else {
    clearDisplay();
  }
});

init();
</script>
</body>
</html>
